import base64
import math
from datetime import datetime, timedelta
import stripe

from odoo.addons.rp_courier_management.functions.price_adjust_rule_fc import PriceAdjustRuleFC

from odoo.addons.rp_courier_management.models.setting.rp_weight_gradient_template import WEIGHT_TYPE
from odoo.addons.rp_courier_management.models.primary.rp_shipper_sale_price_adjustment import ADJUST_FOR
from odoo.addons.rp_courier_management.models.primary.rp_courier_mail_route import FULFILLMENT_TYPE
from odoo.addons.rp_customer_management.models.inherit.res_partner_inherit_for_customer import REL_TYPE
from odoo.addons.rp_courier_management.models.setting.rp_value_added_service import VALUE_CATEGORY
from odoo.exceptions import UserError

ADJUST_RULE_FOR_MARKETING = [('change', '改价'), ('fixed_up', '固定上浮'), ('rate_up', '比率上浮'), ('fixed_down', '固定下浮'), ('rate_down', '比率下浮')]
SHIP_ORDER_STATE = [('one', '第一步'), ('two', '第二步'), ('three', '第三步'), ('four', '第四步'), ('five', '第五步'), ('six', '第六步'), ('unpaid', '待支付'), ('done', '已支付'), ('fail', '支付失败')]


class ShipOrderFC(object):
    """
    Model:
        满足下订单的所有算法逻辑

    Param:
        - env: odoo env

    Function:
        - backdoor_get_available_smr: odoo后台获取可用托运商邮路 及其价格
        - backdoor_get_available_sva: odoo后台获取可用增值服务 及其价格
        - backdoor_get_fulfillment: odoo后台获取可用的头尾程 及其价格
        - backdoor_get_available_customer_address: odoo后台获取可用的客户地址
        - compute_arrive_time: 计算包裹到达时间
    """

    def __init__(self, env):
        self.env = env

    def backdoor_get_available_smr(self, send_city, receive_city, package_type, parcel_total_weight, parcel_total_volume):
        """
        odoo后台获取可用托运商邮路，计算相应价格，创建托运商邮路向导模型

        :param send_city: 发件国家(res.city)
        :param receive_city: 收件国家(res.city)
        :param package_type: 包裹类型(rp.package.type)
        :param parcel_total_weight: 包裹总重量(Float)
        :param parcel_total_volume: 包裹总体积(Float)

        @return smr_values(可以直接创建rp.available.shipper.mail.route.tran的json)
        """

        # 1、查询可用托运商邮路
        sale_smr_rcds = self._get_available_smr(send_city, receive_city, package_type, parcel_total_weight, parcel_total_volume)

        available_asmr_values = []
        for ssmr in sale_smr_rcds:
            # 2、计算每一个可用托运商邮路的价格
            value = self._compute_smr_price(ssmr, parcel_total_weight, parcel_total_volume)
            if value:
                # 3、拼接values
                available_asmr_values.append(value)

        return available_asmr_values

    def backdoor_get_available_sva(self, shipper_mail_route):
        """
        odoo后台获取可用增值服务，计算相应价格，创建可用增值服务向导模型

        :param shipper_mail_route: 托运商邮路(rp.shipper.mail.route)

        @return available_sva_values(可以直接创建rp.available.shipper.value.added.tran的json)
        """
        shipper = shipper_mail_route.shipper_id  # 托运商

        # 1、获取可用增值服务
        value_added_ids = shipper_mail_route.value_added_ids.filtered(lambda i: i.category == VALUE_CATEGORY[1][0])

        # 2、获取增值服务的价格表
        value_added_price_ids = shipper.sale_template_id.rp_shipper_sale_template_value_added_line_ids

        available_sva_values = []
        for sva in value_added_ids:
            # 3、获取增值服务的销售调价
            all_price_adjustments = shipper.sale_template_id.price_adjustment_ids.filtered(lambda i: all([i.adjust_for == ADJUST_FOR[2][0]]))
            this_price_adjustments = shipper.sale_template_id.price_adjustment_ids.filtered(lambda i: all([i.adjust_for == ADJUST_FOR[3][0],
                                                                                                           i.shipper_sale_template_value_added_id.shipper_value_added_id.category == VALUE_CATEGORY[1][0],
                                                                                                           i.shipper_sale_template_value_added_id.shipper_value_added_id.id == sva.id]))
            price_adjustments = all_price_adjustments | this_price_adjustments

            # 4、获取该增值服务的价格
            tag_price = value_added_price_ids.filtered(lambda i: i.shipper_value_added_id == sva).sale_price

            # 5、计算调后价
            if price_adjustments:
                sale_adjusted_price = tag_price
                for pa in price_adjustments:
                    sale_adjusted_price = PriceAdjustRuleFC(self.env).get_adjusted_price(sale_adjusted_price, pa.price_adjust_rule_id)
            else:
                sale_adjusted_price = tag_price

            # 6、创建可用增值服务的向导模型
            values = {'value_added_id': sva.id,
                      'value_added_type': sva.type,
                      'value_added_category': sva.category,
                      'tag_price': tag_price,
                      'sale_adjusted_price': sale_adjusted_price,
                      'used_sale_price_adjust_ids': [(6, 0, price_adjustments.ids)]}
            available_sva_values.append(values)

        return available_sva_values

    def backdoor_get_fulfillment(self, smr):
        """
        获取当前托运商邮路的头尾程及其价格

        :param
        :param smr: 托运商邮路(rp.shipper.mail.route)

        @return available_sff_values(可以直接创建rp.available.shipper.fulfillment.tran的json)
        """
        shipper_mail_route = smr  # 托运商邮路
        shipper_sale_template = smr.shipper_id.sale_template_id  # 托运商定价模板

        available_sff_values = []
        for code, desc in FULFILLMENT_TYPE:
            value_added_id = self.env['rp.value.added.service'].search([('name', '=', code.strip()), ('category', '=', 'one')])
            values = {
                'id': value_added_id.id,
                'operation_name': desc,
                'operation_type': code}

            # 1、判断头尾程是否可用
            if eval('shipper_mail_route.operation_' + code):

                # 2、获得标价
                value_added_for_shipper = shipper_sale_template.rp_shipper_sale_template_value_added_line_ids.filtered(lambda i: i.shipper_value_added_id.name == code)
                tag_price = value_added_for_shipper.sale_price

                # 3、计算销售调后价
                price_adjustments = shipper_sale_template.price_adjustment_ids.filtered(lambda i: all([i.adjust_for == ADJUST_FOR[3][0],
                                                                                                       i.shipper_sale_template_value_added_id.shipper_value_added_id.category == VALUE_CATEGORY[0][0],
                                                                                                       i.shipper_sale_template_value_added_id.shipper_value_added_id.name == code]))
                if price_adjustments:
                    sale_adjusted_price = tag_price
                    for pa in price_adjustments:
                        sale_adjusted_price = PriceAdjustRuleFC(self.env).get_adjusted_price(sale_adjusted_price, pa.price_adjust_rule_id)
                else:
                    sale_adjusted_price = tag_price

                # 4、创建可用头尾程的向导模型
                values.update({'available_flag': True,
                               'tag_price': tag_price,
                               'sale_adjusted_price': sale_adjusted_price,
                               'used_sale_price_adjust_ids': [(6, 0, price_adjustments.ids)]})
            available_sff_values.append(values)

        return available_sff_values

    def backdoor_get_available_customer_address(self, customer):
        """
        odoo后台获取可用的客户地址

        :param customer: 客户(rp.customer)

        @return available_customer_address(rp.available.customer.address.tran)
        """
        # 1。查询该客户地址
        address_rcds = self.env['res.partner'].search([('rel_type', '=', REL_TYPE[0][0]), ('customer_address_id', '=', customer.id)])
        # 2、去掉“国家省市区邮编”后地址去重
        address_rcds = self._customer_address_remove_duplicate(address_rcds)

        # 3、创建可用地址
        available_customer_address = self.env['rp.available.customer.address.tran'].browse()
        for ad in address_rcds:
            value = {'customer_id': customer.id,
                     'first_name': ad.first_name,
                     'last_name': ad.last_name,
                     'phone': ad.mobile,
                     'street': ad.street,
                     'address_type': ad.address_type,
                     'email': ad.email,
                     'company_name': ad.contact_company_name}
            address = self.env['rp.available.customer.address.tran'].create([value])
            available_customer_address = available_customer_address | address

        return available_customer_address

    def _customer_address_remove_duplicate(self, address_rcds):
        """
        根据给定的地址记录集，进行去重
        在选择可用地址的时候，因为国家及邮编在第一步就输入了，所以获取地址时应当将“国家省市区邮编”排除在外。去除“国家省市区邮编”的记录集难免存在重复，因此需要去重

        :param address_rcds: 地址记录集(res.partner)

        @return unique_addres_rcds(res.partner)
        """
        unique_identifiers = set()
        unique_address_rcds = self.env['res.partner'].browse()

        for address in address_rcds:
            # 创建一个标识符，包含所有不为 False 的字段值
            identifier = (address.first_name, address.last_name, address.mobile, address.contact_company_name, address.street, address.email)

            # 检查是否已存在相同的标识符
            if identifier not in unique_identifiers:
                unique_identifiers.add(identifier)
                unique_address_rcds += address

        return unique_address_rcds

    def _get_available_smr(self, send_city, receive_city, package_type, parcel_total_weight, limit=None):
        """
        根据启运地城市、目的地城市、包裹类型、实际重量获取所有可用的 ”托运商邮路价格明细“

        :param send_city: 发件国家(res.city)
        :param receive_city: 收件国家(res.city)
        :param package_type: 包裹类型(rp.package.type)
        :param parcel_total_weight: 包裹总重量(Float)
        :param limit: 获取可用托运商服务的条数(默认不限制)

        @return sale_smr_rcds(rp.shipper.sale.template.mail.route.line)
        """
        domain_list = []
        # 查询启运地
        ship_from_group = self.env['res.country.group'].search([('city_ids', 'in', [send_city.id])])
        domain_list.append(('ship_from_country_group_ids', 'in', ship_from_group.ids))
        # 查询目的地
        ship_to_group = self.env['res.country.group'].search([('city_ids', 'in', [receive_city.id])])
        domain_list.append(('ship_to_country_group_ids', 'in', ship_to_group.ids))
        # 筛选包裹类型
        domain_list.append(('package_type_ids', 'in', [package_type.id]))
        # 匹配重量区间
        domain_list.extend([('inf', '<', parcel_total_weight), ('sup', '>', parcel_total_weight)])

        sale_smr_rcds = self.env['rp.shipper.sale.template.mail.route.line'].search(domain_list, limit=limit)

        return sale_smr_rcds

    def _compute_smr_price(self, ssmr, parcel_total_weight, parcel_total_volume):
        """
        计算托运商邮路的价格

        :param ssmr: 托运商邮路定价明细(rp.shipper.sale.template.mail.route.line)
        :param parcel_total_weight: 包裹总重量(Float)
        :param parcel_total_volume: 包裹总体积(Float)

        @return value: 可被odooORM识别的字典对象
        """
        shipper = ssmr.shipper_id  # 托运商
        shipper_mail_route = ssmr.shipper_mail_route_id  # 托运商邮路
        courier_mail_route = ssmr.shipper_mail_route_id.courier_mail_route_id  # 承运商邮路
        shipper_sale_template = ssmr.rp_shipper_sale_template_id  # 托运商定价模板

        # 1、计算实际重量
        volume_weight = courier_mail_route.volume_weight_id.volume_weight  # 体积重
        if volume_weight:  # 防止被除数为0
            actual_weight = max(parcel_total_weight, parcel_total_volume / volume_weight)  # 实际重量
        else:
            actual_weight = parcel_total_weight

        # 2、根据实际重量判断重量区间
        weight_gradient_type = ssmr.weight_gradient_type  # 重量梯度类型
        wl = self.env['rp.shipper.sale.template.weight.line']
        if weight_gradient_type == WEIGHT_TYPE[0][0]:  # 左开右闭:
            wl = ssmr.shipper_sale_template_line_ids.filtered(lambda i: i.min_weight < actual_weight <= i.max_weight)
        elif weight_gradient_type == WEIGHT_TYPE[1][0]:  # 左闭右开:
            wl = ssmr.shipper_sale_template_line_ids.filtered(lambda i: i.min_weight <= actual_weight < i.max_weight)
        if not wl:  # 没有重量区间匹配
            return False

        # 3、根据重量区间计算进位数量
        carry_number = (actual_weight - wl.min_weight) // wl.carry_value
        carry_flag = True if (actual_weight - wl.min_weight) % wl.carry_value != 0 else False
        carry_number = carry_number + 1 if carry_flag else carry_number

        # 4、根据进位数量计算取整重量
        rounded_weight = wl.min_weight + wl.carry_value * carry_number

        # 5、根据进位数量计算定价
        tag_price = wl.start_price + wl.carry_price * carry_number

        # 6、寻找调价，计算调后价格
        all_route_adjustments = shipper_sale_template.price_adjustment_ids.filtered(lambda i: i.adjust_for == ADJUST_FOR[0][0])
        this_route_adjustments = shipper_sale_template.price_adjustment_ids.filtered(lambda i: all([i.adjust_for == ADJUST_FOR[1][0],
                                                                                                    i.shipper_sale_template_mail_route_id == ssmr]))
        route_adjustments = all_route_adjustments | this_route_adjustments
        if route_adjustments:
            sale_adjusted_price = tag_price
            for ra in route_adjustments:
                sale_adjusted_price = PriceAdjustRuleFC(self.env).get_adjusted_price(sale_adjusted_price, ra.price_adjust_rule_id)
        else:
            sale_adjusted_price = tag_price
        # 7 计算达到时间
        today = datetime.now().date()
        res = self.compute_arrive_time(today, shipper_mail_route.id, ssmr.aging_id.id)

        # 9、拼接返回的value
        value = {'shipper_mail_route_id': shipper_mail_route.id,
                 'shipper_id': shipper.id,
                 'actual_weight': actual_weight,
                 'rounded_weight': rounded_weight,
                 'aging_id': ssmr.aging_id.id,
                 'expect_arrive_date': res['arrive_time'],
                 'tag_price': tag_price,
                 'express_category': ssmr.express_category,
                 'same_city_flag': ssmr.same_city_flag,
                 'same_province_flag': ssmr.same_province_flag,
                 'sale_adjusted_price': sale_adjusted_price,
                 'used_sale_price_adjust_ids': [(6, 0, route_adjustments.ids)]}
        return value

    def _merge_dicts(self, d1, d2):
        """ 字典合并，遇到相同key合并为odoo记录集 """
        merged = {}
        for k in set(d1) | set(d2):  # 合并两个字典的键
            if k in d1 and k in d2:
                if isinstance(d1[k]) != isinstance(d2[k]):
                    merged[k] = d1[k] if isinstance(d1[k]) != 'bool' else d2[k]
                else:
                    merged[k] = d1[k] | d2[k]
            elif k in d1:
                # 只在第一个字典中有的键
                merged[k] = d1[k]
            else:
                # 只在第二个字典中有的键
                merged[k] = d2[k]
        return merged

    """
    -------------------------------------------------------
    营销调价函数
    """

    @classmethod
    def get_price_for_marketing(cls, adjustment_before, marketing_price_adjust_line):
        """
        Param:
        -adjustment_before:调价前的价格
        -marketing_price_adjust_line:调价明细

        Return:
        -adjustment_after:调价后的价格
        """
        adjustment_after = adjustment_before
        if marketing_price_adjust_line.rp_price_adjust_rule_id.rule == ADJUST_RULE_FOR_MARKETING[2][0]:
            adjustment_after = adjustment_before * (1 + marketing_price_adjust_line.rp_price_adjust_rule_id.rated_float)
        if marketing_price_adjust_line.rp_price_adjust_rule_id.rule == ADJUST_RULE_FOR_MARKETING[4][0]:
            adjustment_after = adjustment_before * (1 - marketing_price_adjust_line.rp_price_adjust_rule_id.rated_float)
        if marketing_price_adjust_line.rp_price_adjust_rule_id.rule == ADJUST_RULE_FOR_MARKETING[1][0]:
            adjustment_after = adjustment_before + marketing_price_adjust_line.rp_price_adjust_rule_id.fixed_float
        if marketing_price_adjust_line.rp_price_adjust_rule_id.rule == ADJUST_RULE_FOR_MARKETING[3][0]:
            adjustment_after = adjustment_before - marketing_price_adjust_line.rp_price_adjust_rule_id.fixed_float
        if marketing_price_adjust_line.rp_price_adjust_rule_id.rule == ADJUST_RULE_FOR_MARKETING[0][0]:
            adjustment_after = marketing_price_adjust_line.rp_price_adjust_rule_id.changed_price
        return adjustment_after

    # 界面刷新功能
    def action_reload(self):
        return {
            'type': 'ir.actions.client',
            'tag': 'reload',
        }

    # 按钮：上一步
    def button_change_btn_state_back(self, id):
        ship_id = self.env['rp.ship.order'].browse(id)
        n = 0
        if ship_id.btn_state != 'one':
            for state in SHIP_ORDER_STATE:
                if ship_id.btn_state == state[0]:
                    n = n - 1
                    ship_id.write({'btn_state': SHIP_ORDER_STATE[n][0]})
                    break
                else:
                    n += 1
        else:
            ship_id.no_have_back = True

    # 按钮：下一步
    def button_change_btn_state_next(self, id):
        ship_id = self.env['rp.ship.order'].browse(id)
        ship_id.no_have_back = False
        if ship_id.step_one and ship_id.btn_state == 'one':
            ship_id.write({'btn_state': SHIP_ORDER_STATE[1][0],
                           'is_frist_step': True})
            return True
        if ship_id.step_two and ship_id.btn_state == 'two':
            ship_id.write({'btn_state': SHIP_ORDER_STATE[2][0],
                           'is_second_step': True})
            return True
        if ship_id.step_three and ship_id.btn_state == 'three':
            ship_id.write({'btn_state': SHIP_ORDER_STATE[3][0],
                           'is_third_step': True})
            return True
        if ship_id.step_four and ship_id.btn_state == 'four':
            ship_id.write({'btn_state': SHIP_ORDER_STATE[4][0],
                           'is_fourth_step': True})
            return True
        if ship_id.step_five and ship_id.btn_state == 'five':
            ship_id.write({'btn_state': SHIP_ORDER_STATE[5][0],
                           'is_fifth_step': True})
            return True
        if ship_id.step_one and ship_id.step_two and ship_id.step_three and ship_id.step_four and ship_id.step_five and ship_id.step_six and ship_id.btn_state == 'six':
            ship_id.write({'btn_state': SHIP_ORDER_STATE[6][0],
                           'is_sixth_step': True})
            self.action_reload()

    # 计算到达时间
    def compute_arrive_time(self, today, route_id, aging_id):
        route_id = self.env['rp.shipper.mail.route'].browse(route_id)
        aging_id = self.env['rp.aging'].browse(aging_id)

        arrive_date = today
        # 时效统一单位为天，并向上取整
        use_time = math.ceil(round(aging_id.standard_unit_time, 2))
        while use_time > 1:
            # 承运商休假列表
            courier_holiday_line = route_id.courier_id.rp_holiday_template_lines.filtered(lambda x: x.start_time <= arrive_date and \
                                                                                                    x.end_time >= arrive_date)
            if len(courier_holiday_line) > 0:
                arrive_date += timedelta(days=1)
            else:
                arrive_date += timedelta(days=1)
                use_time -= 1
        return {
            'arrive_time': arrive_date
        }

    def gene_payment(self, order_id):
        """
        生成支付单
        """
        # 如果没有支付单创建支付单
        rp_ship_order_payment_id = self.env['rp.payment'].search([('advance_document_id.field_record_id', '=', order_id.id)], limit=1)
        if not rp_ship_order_payment_id:
            # 创建前置记录和客户记录
            advanced_document_id = self.env['rp.related.model'].create({'field_record_id': order_id.id, 'field_name_id': self.env['ir.model'].search([('model', '=', order_id._name)]).id})
            customer_id = self.env['rp.related.model'].create({'field_record_id': order_id.customer_id.id, 'field_name_id': self.env['ir.model'].search([('model', '=', 'rp.customer')]).id})
            values = {'advance_document_id': advanced_document_id.id, 'customer_id': customer_id.id, 'state': 'drift'}
            # 创建支付单
            rp_ship_order_payment_id = self.env['rp.payment'].create(values)
        return rp_ship_order_payment_id

    """
    Service
    """

    def action_to_pay(self, ship_order_id, payment_code):
        """
        前端支付回调成功后，变动Odoo后台订单状态
        """
        # 清除支付链接
        ship_order_id.sudo().write({'pay_link': False})
        # 更新状态，进行支付
        ship_order_id._depends_step_five()
        ship_order_id._depends_step_six()
        ship_order_id.with_context({'payment_code': payment_code}).action_for_pay()
